package moe.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import moe.config.Configuration;
import moe.dao.BaseDao;
import moe.dao.PostDao;
import moe.entity.Post;
import moe.server.CollectionStaticResourcesHandler;
import moe.server.StaticResourcesHandler;
import moe.util.JSONBuilder;
import moe.util.Paging;
import moe.util.UriMappedUtil;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

public class PostHandler extends AbstractHandler {
	private static final Logger log = Logger.getLogger(PostHandler.class.getName());

	private PostDao postDao;

	protected PostHandler(BaseDao.ConnType type) {
		postDao = new PostDao(type);
	}

	public PostHandler() {
		this(BaseDao.ConnType.DEFAULT);
	}

	public void listPosts(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		Paging paging = createPage(query);

		long time = System.currentTimeMillis();
		List<Post> list = postDao.listPosts(paging);
		if (log.isLoggable(Level.FINE)) {
			log.fine("Query(listPosts) time: " + (System.currentTimeMillis() - time) + "ms");
		}
		resolveUrl(list);

		JSONBuilder builder = JSONBuilder.build().callback(getCallback(query))
				.add("posts", list, false);
		bindPaging(builder, paging);
		String json = builder.buildString();

		sendJsonResponse(response, json);
		saveToSession(context, query, list);
	}

	public void getPost(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("id")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		long id = Long.valueOf(query.get("id")[0]);
		Post post = postDao.getPost(id);
		resolveUrl(post);

		String json = JSONBuilder.build().callback(getCallback(query))
				.add("post", post, false).buildString();

		sendJsonResponse(response, json);
	}

	public void searchPosts(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (query.containsKey("collect")) {
			collectPosts(context, query);
			return;
		}

		Paging paging = createPage(query);

		long time = System.currentTimeMillis();
		List<Post> list = postDao.searchPost(query, paging);
		if (log.isLoggable(Level.FINE)) {
			log.fine("Query(searchPosts) time: " + (System.currentTimeMillis() - time) + "ms");
		}
		resolveUrl(list);

		JSONBuilder builder = JSONBuilder.build().callback(getCallback(query))
				.add("posts", list, false);
		bindPaging(builder, paging);
		String json = builder.buildString();

		sendJsonResponse(response, json);
		saveToSession(context, query, list);
	}

	public void takeChildrenCount(HttpContext context,
			Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("id")) {
			throw new RuntimeException("Invalid request");
		}
		String[] ids = query.get("id");
		ids = ids[0].split(",");

		Map<String, Integer> count = postDao.takeChildrenCount(ids);

		String json = JSONBuilder.builder(count).callback(getCallback(query))
				.buildString();

		sendJsonResponse(response, json);
	}

	public void collectPost(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("id")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}

		response.setStatusCode(HttpStatus.SC_OK);
		response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache");

		long id = Long.valueOf(query.get("id")[0]);
		Post post = postDao.getPost(id);
		if (post == null) {
			try {
				response.setEntity(new StringEntity("Invalid id(" + id + ")",
						"text/plain", "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			}
			return;
		}
		resolveUrl(post);
		collectPost(post, response);
	}

	void collectPost(Post post, HttpResponse response) {
		HttpRequest req = new BasicHttpRequest("GET", post.fileUrl);
		HttpResponse res = new BasicHttpResponse(new ProtocolVersion("HTTP", 1,
				1), HttpStatus.SC_BAD_REQUEST, "");
		HttpContext context = new BasicHttpContext();
		StaticResourcesHandler handler = new CollectionStaticResourcesHandler();
		try {
			handler.handle(req, res, context);
		} catch (Exception e) {
			// ignore
		}
		if (res.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
			try {
				response.setEntity(new StringEntity("File:\"" + post.fileUrl
						+ "\" not found", "text/plain", "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			}
			return;
		}

		String collectPath = Configuration.getCollectDir();
		if (!collectPath.endsWith(File.separator)) {
			collectPath += File.separator;
		}
		File collectDir = new File(collectPath);
		if (collectDir.isFile()) {
			try {
				response.setEntity(new StringEntity("CollectDir:\""
						+ collectPath + "\" is a file?!", "text/plain", "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			}
			return;
		}
		if (!collectDir.exists()) {
			collectDir.mkdirs();
		}

		String fileName = post.fileUrl
				.substring(post.fileUrl.lastIndexOf('/') + 1);
		try {
			fileName = URLDecoder.decode(fileName, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// ignore
		}
		fileName = UriMappedUtil.escapeIllegalSystemChar(fileName);
		collectPath += fileName;

		if (new File(collectPath).exists()) {
			try {
				response.setEntity(new StringEntity("File:\"" + collectPath
						+ "\" already exists!", "text/plain", "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			}
			return;
		}
		FileOutputStream collectFile = null;
		try {
			collectFile = new FileOutputStream(collectPath);
			res.getEntity().writeTo(collectFile);
			collectFile.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (collectFile != null) {
					collectFile.close();
				}
			} catch (IOException e) {
				// ignore
			}
		}
		try {
			response.setEntity(new StringEntity("success", "text/plain",
					"UTF-8"));
		} catch (UnsupportedEncodingException e) {
			response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			return;
		}
	}

	private void collectPosts(HttpContext context,
			final Map<String, String[]> query) {
		if (!query.containsKey("collect")) {
			throw new RuntimeException("Invalid request");
		}
		final HttpResponse response = getHttpResponse(context);
		final HttpRequest request = (HttpRequest) context
				.getAttribute(ExecutionContext.HTTP_REQUEST);

		final String collectType = query.get("collect")[0];
		final StringBuilder fileListBuilder = new StringBuilder();
		final String[] errorMessage = new String[1];
		final List<Long> collectFailedIds = new LinkedList<Long>();

		final BlockingQueue<Post> postQueue = new LinkedBlockingQueue<Post>();
		final Thread[] collectThreads = new Thread[2];
		collectThreads[0] = new Thread("PostSearchThread") {
			@Override
			public void run() {
				Thread collectThread = collectThreads[1];
				int pageSize = 200, next = 1;
				Paging paging = new Paging(pageSize);
				List<Post> list = null;
				do {
					paging.setPageNumber(next);
					try {
						list = postDao.searchPost(query, paging);
					} catch (Throwable t) {
						errorMessage[0] = t.getMessage();
						return;
					}
					for (Post post : list) {
						resolveUrl(post);
						boolean offer = false;
						do {
							offer = false;
							try {
								offer = postQueue.offer(post, 5,
										TimeUnit.SECONDS);
							} catch (InterruptedException e) {
								// ignore
							}
							if (!collectThread.isAlive()) {
								return;
							}
						} while (!offer);
					}
					if (paging.hasNext()) {
						next = paging.getNext();
					}
				} while (paging.hasNext());
			}
		};
		collectThreads[1] = new Thread("PostCollectThread") {
			@Override
			public void run() {
				Thread searchThread = collectThreads[0];
				Post post = null;
				while (searchThread.isAlive() || !postQueue.isEmpty()) {
					post = null;
					do {
						try {
							post = postQueue.poll(5, TimeUnit.SECONDS);
						} catch (InterruptedException e) {
							// ignore
						}
						if (post == null && !searchThread.isAlive()) {
							return;
						}
					} while (post == null);
					// do collect
					if (collectType.equals("list")) {
						fileListBuilder
								.append("http://")
								.append(request.getFirstHeader("Host")
										.getValue()).append(post.fileUrl)
								.append("\r\n");
					} else if (collectType.equals("directory")) {
						try {
							collectPost(post, response);
						} catch (Throwable t) {
							collectFailedIds.add(post.id);
						}
					}
				}
			}
		};
		for (Thread thread : collectThreads) {
			thread.setDaemon(true);
			thread.start();
		}

		try {
			for (Thread thread : collectThreads) {
				thread.join();
			}
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}

		HttpEntity entity = null;
		try {
			if (errorMessage[0] != null) {
				entity = new StringEntity("Collection failure, cause:"
						+ errorMessage[0], "text/plain", "UTF-8");
			} else if (!collectFailedIds.isEmpty()) {
				StringBuilder msg = new StringBuilder(
						"Following post collection failure:");
				for (int i = 0, length = collectFailedIds.size() - 1; i < length; i++) {
					msg.append(collectFailedIds.get(i)).append(", ");
				}
				msg.append(collectFailedIds.get(collectFailedIds.size() - 1));
				entity = new StringEntity(msg.toString(), "text/plain", "UTF-8");
			} else if (collectType.equals("list")) {
				entity = new StringEntity(fileListBuilder.toString(),
						"application/octet-stream", "UTF-8");
				response.addHeader("Content-Disposition",
						"attachment; filename=download_list.lst");
			} else if (collectType.equals("directory")) {
				entity = new StringEntity("success", "text/plain", "UTF-8");
			}
		} catch (UnsupportedEncodingException e) {
			response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			return;
		}
		response.setStatusCode(HttpStatus.SC_OK);
		response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
		response.setEntity(entity);
	}

	protected void resolveUrl(Post post) {
		UriMappedUtil.resolveUrl(post);
	}

	private void resolveUrl(List<Post> list) {
		for (Post post : list) {
			resolveUrl(post);
		}
	}
}
